﻿// http://bililite.com/blog/2008/08/20/the-fnsendkeys-plugin/
// insert characters in a textarea or text input field
// special characters are enclosed in {}; use {{} for the { character itself
(function($) {

    $.fn.sendkeys = function sendkeys(x) {
        return this.each(function() {
            var localkeys = $.extend({}, $.metadata && $(this).metadata()['sendkeys'], $(this).data('sendkeys')); // allow for element-specific key functions
            var o = this;
            // IE puts the insertion point at the start of the field on focus. We need to correct that
            // we'll record the actual selection in $.data('sendkeys.selection')
            // Opera is flaky: it impelements both document.selection and element.setSelectionRange, so we have to exclude that
            if (document.selection && !this.setSelectionRange && !$.data(this, 'sendkeys.selection')) {
                var rng = this.createTextRange();
                rng.collapse(false); // append to end
                $.data(this, 'sendkeys.selection', rng);
                $(this).bind('beforedeactivate', function() {
                    $.data(this, 'sendkeys.selection', document.selection.createRange());
                }).bind('focus', function() {
                    $.data(this, 'sendkeys.selection').select();
                });
            }
            this.focus();
            x.replace(/{[^}]*}|[^{]+/g, function(s) {
                (localkeys[s] || $.fn.sendkeys.defaults[s] || simplechar).call(o, s);
            });
            $(o).trigger({ type: 'sendkeys', which: x });
        });
    }; // sendkeys

    // for IE, which isn't smart enough to constrain a selection to a single element
    function constrainAndRecord(rng, o) {
        // Stupid IE can't treat these the same
        if (o.tagName == 'TEXTAREA') {
            var orng = rng.duplicate();
            orng.moveToElementText(o);
        } else {
            orng = o.createTextRange();
        }
        if (orng.compareEndPoints('StartToStart', rng) == 1) rng.setEndPoint('StartToStart', orng);
        if (orng.compareEndPoints('EndToEnd', rng) == -1) rng.setEndPoint('EndToEnd', orng);
        rng.select();
        $.data(o, 'sendkeys.selection', rng);
    } // constrainAndRecord

    if ($('<input>')[0].setSelectionRange) {
        // Standards
        var _simplechar = function(c) {
            var val = this.value;
            var start = this.selectionStart;
            this.value = val.substring(0, start) + c + val.substring(this.selectionEnd);
            this.setSelectionRange(start + c.length, start + c.length);
        };
        var backspace = function(c) {
            var start = this.selectionStart, end = this.selectionEnd;
            var val = this.value;
            if (start != end) {
                this.value = val.substring(0, start) + val.substring(end);
                this.setSelectionRange(start, start);
            } else if (start) {
                // start == end and not start != 0
                this.value = val.substring(0, start - 1) + val.substring(start);
                this.setSelectionRange(start - 1, start - 1);
            }
        };
        var rightarrow = function(c) {
            this.setSelectionRange(this.selectionStart + 1, this.selectionStart + 1);
        };
        var leftarrow = function(c) {
            this.setSelectionRange(this.selectionStart - 1, this.selectionStart - 1);
        };
        var del = function(c) {
            var start = this.selectionStart, end = this.selectionEnd;
            var val = this.value;
            if (start != end) {
                this.value = val.substring(0, start) + val.substring(end);
                this.setSelectionRange(start, start);
            } else {
                this.value = val.substring(0, start) + val.substring(start + 1);
                this.setSelectionRange(start, start);
            }
        };
        var all = function(c) {
            this.setSelectionRange(0, this.value.length);
        };
    } else if (document.selection) {
        // IE
        _simplechar = function(c) {
            // add the char c to the selection
            var rng = $.data(this, 'sendkeys.selection'); // assume o already has the focus
            rng.text = c;
            rng.collapse(false); // move to end of selection
            constrainAndRecord(rng, this);
        };
        backspace = function(c) {
            var rng = $.data(this, 'sendkeys.selection');
            if (rng.text.length == 0) rng.moveStart('character', -1);
            constrainAndRecord(rng, this);
            rng.text = "";
            constrainAndRecord(rng, this); // need to constrain before and after. I don't trust IE
        };
        rightarrow = function(c) {
            var rng = $.data(this, 'sendkeys.selection');
            rng.move('character', 1);
            constrainAndRecord(rng, this);
        }
        leftarrow = function(c) {
            var rng = $.data(this, 'sendkeys.selection');
            rng.move('character', -1);
            constrainAndRecord(rng, this);
        }
        del = function(c) {
            var rng = $.data(this, 'sendkeys.selection');
            if (rng.text.length == 0) rng.moveEnd('character', 1);
            constrainAndRecord(rng, this);
            rng.text = "";
            constrainAndRecord(rng, this); // need to constrain before and after. I don't trust IE
        };
        all = function(c) {
            var rng = this.createTextRange();
            constrainAndRecord(rng, this);
        };
    } else {
        // selections not supported. Just append the character
        _simplechar = function(c) {
            this.value += c;
        };
        backspace = del = function(c) {
            this.value = "";
        }
        rightarrow = leftarrow = all = function() { };
    }

    // simulate a keypress (not keydown/keyup) for simplechar. Any other key has to be done by hand in the code
    simplechar = function(c) {
        _simplechar.call(this, c);
        for (var i = 0; i < c.length; ++i) {
            var x = c.charCodeAt(i);
            $(this).trigger({ type: 'keypress', keyCode: x, which: x, charCode: x });
        }
    }

    // add the functions publicly so they can be overridden
    $.fn.sendkeys.defaults = {
        '{backspace}': backspace,
        '{rightarrow}': rightarrow,
        '{leftarrow}': leftarrow,
        '{del}': del,
        '{selectall}': all,
        '{{}': function() { simplechar.call(this, '{') },
        'simplechar': simplechar // make it publicly callable
    };

})(jQuery)